<style>
#custom-dns-current td.long {
  word-break: break-all;
}
</style>

<h2>Custom DNS</h2>

<p class="text-warning">This is an advanced configuration page.</p>

<p>It is possible to set custom DNS records on domains hosted here.</p>

<h3>Set custom DNS records</h3>

<p>You can set additional DNS records, such as if you have a website running on another server, to add DKIM records for external mail providers, or for various confirmation-of-ownership tests.</p>

<form class="form-horizontal" role="form" onsubmit="do_set_custom_dns(); return false;">
  <div class="form-group">
    <label for="customdnsQname" class="col-sm-1 control-label">Name</label>
    <div class="col-sm-10">
      <table style="max-width: 400px">
      <tr><td>
        <input type="text" class="form-control" id="customdnsQname" placeholder="subdomain">
      </td><td style="padding: 0 1em; font-weight: bold;">.</td><td>
        <select id="customdnsZone" class="form-control"> </select>
      </td></tr></table>
      <div class="text-info" style="margin-top: .5em">Leave the left field blank to set a record on the chosen domain name, or enter a subdomain.</div>
    </div>
  </div>
  <div class="form-group">
    <label for="customdnsType" class="col-sm-1 control-label">Type</label>
    <div class="col-sm-10">
      <select id="customdnsType" class="form-control" style="max-width: 400px" onchange="show_customdns_rtype_hint()">
        <option value="A" data-hint="Enter an IPv4 address (i.e. a dotted quad, such as 123.456.789.012).  The 'local' alias sets the record to this box's public IPv4 address.">A (IPv4 address)</option>
        <option value="AAAA" data-hint="Enter an IPv6 address.  The 'local' alias sets the record to this box's public IPv6 address.">AAAA (IPv6 address)</option>
        <option value="CAA" data-hint="Enter a CA that can issue certificates for this domain in the form of FLAG TAG VALUE. (0 issuewild &quot;letsencrypt.org&quot;)">CAA (Certificate Authority Authorization)</option>
        <option value="CNAME" data-hint="Enter another domain name followed by a period at the end (e.g. mypage.github.io.).">CNAME (DNS forwarding)</option>
        <option value="TXT" data-hint="Enter arbitrary text.">TXT (text record)</option>
        <option value="MX" data-hint="Enter record in the form of PRIORITY DOMAIN., including trailing period (e.g. 20 mx.example.com.).">MX (mail exchanger)</option>
        <option value="SRV" data-hint="Enter record in the form of PRIORITY WEIGHT PORT TARGET., including trailing period (e.g. 10 10 5060 sip.example.com.).">SRV (service record)</option>
        <option value="SSHFP" data-hint="Enter record in the form of ALGORITHM TYPE FINGERPRINT.">SSHFP (SSH fingerprint record)</option>
        <option value="NS" data-hint="Enter a hostname to which this subdomain should be delegated to">NS (DNS subdomain delegation)</option>
      </select>
    </div>
  </div>
  <div class="form-group">
    <label for="customdnsValue" class="col-sm-1 control-label">Value</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" id="customdnsValue" placeholder="">
      <div id="customdnsTypeHint" class="text-info" style="margin-top: .5em"></div>
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-1 col-sm-11">
      <button type="submit" class="btn btn-primary">Set Record</button>
    </div>
  </div>
</form>

<div style="text-align: right; font-size; 90%; margin-top: 1em;">
  sort by:
  <a href="#" onclick="window.miab_custom_dns_data_sort_order='qname'; show_current_custom_dns_update_after_sort(); return false;">domain name</a>
  |
  <a href="#" onclick="window.miab_custom_dns_data_sort_order='created'; show_current_custom_dns_update_after_sort(); return false;">created</a>
</div>
<table id="custom-dns-current" class="table" style="width: auto; display: none; margin-top: 0;">
  <thead>
    <th>Domain Name</th>
    <th>Record Type</th>
    <th>Value</th>
    <th></th>
  </thead>
  <tbody>
    <tr><td colspan="4">Loading...</td></tr>
  </tbody>
</table>

<h3>Using a secondary nameserver</h3>

<p>If your TLD requires you to have two separate nameservers, you can either set up <a href="#" onclick="return show_panel('external_dns')">external DNS</a> and ignore the DNS server on this box entirely, or use the DNS server on this box but add a secondary (aka &ldquo;slave&rdquo;) nameserver.</p>
<p>If you choose to use a secondary nameserver, you must find a secondary nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the secondary nameserver service, enter the hostname (not the IP address) of <em>their</em> secondary nameserver in the box below.</p>

<form class="form-horizontal" role="form" onsubmit="do_set_secondary_dns(); return false;">
  <div class="form-group">
    <label for="secondarydnsHostname" class="col-sm-1 control-label">Hostname</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" id="secondarydnsHostname" placeholder="ns1.cloudprovider.com">
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-1 col-sm-11">
      <button type="submit" class="btn btn-primary">Update</button>
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-1 col-sm-11">
      <p class="small">
        Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).
        To enable zone transfers to additional servers without listing them as secondary nameservers, add an IP address or subnet using <code>xfr:10.20.30.40</code> or <code>xfr:10.0.0.0/8</code>.
      </p>
      <p id="secondarydns-clear-instructions" style="display: none" class="small">
        Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.
      </p>
    </div>
  </div>
</form>


<h3>Custom DNS API</h3>

<p>Use your box&rsquo;s DNS API to set custom DNS records on domains hosted here. For instance, you can create your own dynamic DNS service.</p>

<p>Usage:</p>

<pre>curl -X <b>VERB</b> [-d "<b>value</b>"] --user {email}:{password} https://{{hostname}}/admin/dns/custom[/<b>qname</b>[/<b>rtype</b>]]</pre>

<p>(Brackets denote an optional argument.)</p>

<h4>Verbs</h4>

<table class="table">
<thead><th>Verb</th> <th>Usage</th></thead>
<tr><td>GET</td> <td>Returns matching custom DNS records as a JSON array of objects. Each object has the keys <code>qname</code>, <code>rtype</code>, and <code>value</code>. The optional <code>qname</code> and <code>rtype</code> parameters in the request URL filter the records returned in the response. The request body (<code>-d "..."</code>) must be omitted.</td></tr>
<tr><td>PUT</td> <td>Sets a custom DNS record replacing any existing records with the same <code>qname</code> and <code>rtype</code>. Use PUT (instead of POST) when you only have one value for a <code>qname</code> and <code>rtype</code>, such as typical <code>A</code> records (without round-robin).</td></tr>
<tr><td>POST</td> <td>Adds a new custom DNS record. Use POST when you have multiple <code>TXT</code> records or round-robin <code>A</code> records. (PUT would delete previously added records.)</td></tr>
<tr><td>DELETE</td> <td>Deletes custom DNS records. If the request body (<code>-d "..."</code>) is empty or omitted, deletes all records matching the <code>qname</code> and <code>rtype</code>. If the request body is present, deletes only the record matching the <code>qname</code>, <code>rtype</code> and value.</td></tr>
</table>

<h4>Parameters</h4>

<table class="table">
<thead><th>Parameter</th> <th>Value</th></thead>
<tr><td>email</td> <td>The email address of any administrative user here.</td></tr>
<tr><td>password</td> <td>That user&rsquo;s password.</td></tr>
<tr><td>qname</td> <td>The fully qualified domain name for the record you are trying to set. It must be one of the domain names or a subdomain of one of the domain names hosted on this box. (Add mail users or aliases to add new domains.)</td></tr>
<tr><td>rtype</td> <td>The resource type. Defaults to <code>A</code> if omitted. Possible values: <code>A</code> (an IPv4 address), <code>AAAA</code> (an IPv6 address), <code>TXT</code> (a text string), <code>CNAME</code> (an alias, which is a fully qualified domain name &mdash; don&rsquo;t forget the final period), <code>MX</code>, <code>SRV</code>, <code>SSHFP</code>, <code>CAA</code> or <code>NS</code>.</td></tr>
<tr><td>value</td> <td>For PUT, POST, and DELETE, the record&rsquo;s value. If the <code>rtype</code> is <code>A</code> or <code>AAAA</code> and <code>value</code> is empty or omitted, the IPv4 or IPv6 address of the remote host is used (be sure to use the <code>-4</code> or <code>-6</code> options to curl). This is handy for dynamic DNS!</td></tr>
</table>

<p>Strict <a href="http://tools.ietf.org/html/rfc4408">SPF</a> and <a href="https://datatracker.ietf.org/doc/draft-kucherawy-dmarc-base/?include_text=1">DMARC</a> records will be added to all custom domains unless you override them.</p>

<h4>Examples:</h4>

<p>Try these examples. For simplicity the examples omit the <code>--user me@mydomain.com:yourpassword</code> command line argument which you must fill in with your email address and password.</p>

<pre># sets laptop.mydomain.com to point to the IP address of the machine you are executing curl on
curl -X PUT https://{{hostname}}/admin/dns/custom/laptop.mydomain.com

# deletes that record and all A records for that domain name
curl -X DELETE https://{{hostname}}/admin/dns/custom/laptop.mydomain.com

# sets a CNAME alias
curl -X PUT -d "bar.mydomain.com." https://{{hostname}}/admin/dns/custom/foo.mydomain.com/cname

# deletes that CNAME and all CNAME records for that domain name
curl -X DELETE https://{{hostname}}/admin/dns/custom/foo.mydomain.com/cname

# adds a TXT record using POST to preserve any previous TXT records
curl -X POST -d "some text here" https://{{hostname}}/admin/dns/custom/foo.mydomain.com/txt

# deletes that one TXT record while preserving other TXT records
curl -X DELETE -d "some text here" https://{{hostname}}/admin/dns/custom/foo.mydomain.com/txt
</pre>

<script>
function show_custom_dns() {
  api(
    "/dns/secondary-nameserver",
    "GET",
    { },
    function(data) {
      $('#secondarydnsHostname').val(data.hostnames.join(' '));
      $('#secondarydns-clear-instructions').toggle(data.hostnames.length > 0);
    });

  api(
    "/dns/zones",
    "GET",
    { },
    function(data) {
      $('#customdnsZone').text('');
      for (var i = 0; i < data.length; i++) {
        $('#customdnsZone').append($('<option/>').text(data[i]));
      }
    });

  show_current_custom_dns();
  show_customdns_rtype_hint();
}

function show_current_custom_dns() {
  api(
    "/dns/custom",
    "GET",
    { },
    function(data) {
      if (data.length > 0)
        $('#custom-dns-current').fadeIn();
      else
        $('#custom-dns-current').fadeOut();
      window.miab_custom_dns_data = data;
      show_current_custom_dns_update_after_sort();
    });
}

function show_current_custom_dns_update_after_sort() {
      var data = window.miab_custom_dns_data;
      var sort_key = window.miab_custom_dns_data_sort_order || "qname";

      data.sort(function(a, b) { return a["sort-order"][sort_key] - b["sort-order"][sort_key] });

      var tbody = $('#custom-dns-current').find("tbody");
      tbody.text('');
      var last_zone = null;
      for (var i = 0; i < data.length; i++) {
        if (sort_key == "qname" && data[i].zone != last_zone) {
          var r = $("<tr><th colspan=4 style='background-color: #EEE'></th></tr>");
          r.find("th").text(data[i].zone);
          tbody.append(r);
          last_zone = data[i].zone;
        }

        var tr = $("<tr/>");
        tbody.append(tr);
        tr.attr('data-qname', data[i].qname);
        tr.attr('data-rtype', data[i].rtype);
        tr.attr('data-value', data[i].value);
        tr.append($('<td class="long"/>').text(data[i].qname));
        tr.append($('<td/>').text(data[i].rtype));
        tr.append($('<td class="long" style="max-width: 40em"/>').text(data[i].value));
        tr.append($('<td>[<a href="#" onclick="return delete_custom_dns_record(this)">delete</a>]</td>'));
      }
}

function delete_custom_dns_record(elem) {
  var qname = $(elem).parents('tr').attr('data-qname');
  var rtype = $(elem).parents('tr').attr('data-rtype');
  var value = $(elem).parents('tr').attr('data-value');
  do_set_custom_dns(qname, rtype, value, "DELETE");
  return false;
}

function do_set_secondary_dns() {
 api(
    "/dns/secondary-nameserver",
    "POST",
    {
      hostnames: $('#secondarydnsHostname').val()
    },
    function(data) {
      if (data == "") return; // nothing updated
      show_modal_error("Secondary DNS", $("<pre/>").text(data));
      $('#secondarydns-clear-instructions').slideDown();
    },
    function(err) {
      show_modal_error("Secondary DNS", $("<pre/>").text(err));
    });
}

function do_set_custom_dns(qname, rtype, value, method) {
  if (!qname) {
    if ($('#customdnsQname').val() != '')
      qname = $('#customdnsQname').val() + '.' + $('#customdnsZone').val();
    else
      qname = $('#customdnsZone').val();
    rtype = $('#customdnsType').val();
    value = $('#customdnsValue').val();
    method = 'POST';
  }

  api(
    "/dns/custom/" + qname + "/" + rtype,
    method,
    value,
    function(data) {
      if (data == "") return; // nothing updated
      show_modal_error("Custom DNS", $("<pre/>").text(data));
      show_current_custom_dns();
    },
    function(err) {
      show_modal_error("Custom DNS (Error)", $("<pre/>").text(err));
    });
}

function show_customdns_rtype_hint() {
  $('#customdnsTypeHint').text($("#customdnsType").find('option:selected').attr('data-hint'));
}
</script>
